use axum::{
    extract::{Multipart, Path, Query, State},
    http::{header, StatusCode},
    response::{IntoResponse, Json},
};
use serde::{Deserialize, Serialize};

use crate::error::AppError;
use crate::models::Claims;
use crate::utils::dvc::generate_dvc_file;
use crate::utils::{hash::calculate_file_hash, AuthenticatedUser};

#[derive(Debug, Deserialize)]
pub struct ListQuery {
    pub page: Option<i64>,
    pub page_size: Option<i64>,
    pub user_only: Option<bool>,
}

#[derive(Debug, Serialize)]
pub struct PaginatedResponse<T> {
    pub items: Vec<T>,
    pub total: i64,
    pub page: i64,
    pub page_size: i64,
}

#[derive(Debug, Deserialize)]
pub struct MetadataUpload {
    pub name: String,
    pub description: Option<String>,
    pub is_public: Option<bool>,
}

pub async fn list_data_items(
    AuthenticatedUser(user): AuthenticatedUser,
    Query(params): Query<ListQuery>,
    State(state): State<crate::AppState>,
) -> Result<Json<PaginatedResponse<crate::models::DataItemWithUser>>, AppError> {
    println!(
        "DEBUG: list_data_items handler called for user: {}",
        user.email
    );
    let page = params.page.unwrap_or(1);
    let page_size = params.page_size.unwrap_or(20);
    let user_only = params.user_only.unwrap_or(false);

    // Get real user_id from database using email
    let user_id = state.data_service.get_user_id_by_email(&user.email).await?;

    let (items, total) = if user_only {
        // Show only user's own items
        state
            .data_service
            .list_data_items_with_user(user_id, Some(page), Some(page_size))
            .await?
    } else {
        // Show user's own items + all public items
        state
            .data_service
            .list_user_and_public_data_items_with_user(user_id, Some(page), Some(page_size))
            .await?
    };

    Ok(Json(PaginatedResponse {
        items,
        total,
        page,
        page_size,
    }))
}

pub async fn list_public_data_items(
    Query(params): Query<ListQuery>,
    State(state): State<crate::AppState>,
) -> Result<Json<PaginatedResponse<crate::models::DataItemWithUser>>, AppError> {
    let page = params.page.unwrap_or(1);
    let page_size = params.page_size.unwrap_or(20);

    let (items, total) = state
        .data_service
        .list_public_data_items_with_user(Some(page), Some(page_size))
        .await?;

    Ok(Json(PaginatedResponse {
        items,
        total,
        page,
        page_size,
    }))
}

pub async fn get_data_item(
    Path(id): Path<i64>,
    AuthenticatedUser(user): AuthenticatedUser,
    State(state): State<crate::AppState>,
) -> Result<Json<crate::models::DataItem>, AppError> {
    // Get real user_id from database using email
    let user_id = state.data_service.get_user_id_by_email(&user.email).await?;

    let item = state
        .data_service
        .get_data_item(id, user_id)
        .await?
        .ok_or(AppError::NotFound("Data item not found".to_string()))?;

    Ok(Json(item))
}

pub async fn upload_data(
    State(state): State<crate::AppState>,
    claims: Claims,
    mut multipart: Multipart,
) -> Result<impl IntoResponse, AppError> {
    println!(
        "DEBUG: Unified upload request received from user: {}",
        claims.sub
    );

    // Get the user from database using the claims
    let user = state.auth_service.get_user_by_email(&claims.sub).await?;

    let mut files = Vec::new();
    let mut project = None;
    let mut source = None;
    let mut description = None;
    let mut is_public = false;
    let mut _parent_id: Option<i64> = None;

    while let Some(field) = multipart
        .next_field()
        .await
        .map_err(|e| AppError::Internal(format!("Failed to read multipart field: {}", e)))?
    {
        let name = field.name().unwrap_or("").to_string();

        match name.as_str() {
            "files" => {
                let file_name = field.file_name().unwrap_or("unknown").to_string();
                let file_data = field
                    .bytes()
                    .await
                    .map_err(|e| AppError::Internal(format!("Failed to read file data: {}", e)))?;

                println!(
                    "DEBUG: Received file: {} ({} bytes)",
                    file_name,
                    file_data.len()
                );
                files.push((file_name, file_data));
            }
            "project" => {
                project =
                    Some(field.text().await.map_err(|e| {
                        AppError::Internal(format!("Failed to read project: {}", e))
                    })?);
            }
            "source" => {
                source =
                    Some(field.text().await.map_err(|e| {
                        AppError::Internal(format!("Failed to read source: {}", e))
                    })?);
            }
            "description" => {
                description = Some(field.text().await.map_err(|e| {
                    AppError::Internal(format!("Failed to read description: {}", e))
                })?);
            }
            "parent_id" => {
                let parent_id_str = field
                    .text()
                    .await
                    .map_err(|e| AppError::Internal(format!("Failed to read parent_id: {}", e)))?;
                _parent_id = parent_id_str.parse().ok();
            }
            "is_public" => {
                let is_public_str = field
                    .text()
                    .await
                    .map_err(|e| AppError::Internal(format!("Failed to read is_public: {}", e)))?;
                is_public = is_public_str.parse().unwrap_or(false);
            }
            _ => {
                println!("DEBUG: Ignoring unknown field: {}", name);
            }
        }
    }

    if files.is_empty() {
        return Err(AppError::BadRequest("No files provided".to_string()));
    }

    // Process each file with unified workflow
    let mut uploaded_items = Vec::new();
    for (file_name, file_data) in files {
        // Calculate file hash
        let file_hash = calculate_file_hash(&file_data)
            .map_err(|e| AppError::Internal(format!("Failed to calculate file hash: {}", e)))?;

        // 1. Store data file in DVC cache
        let _stored_hash = state
            .storage_service
            .store_data_file(&file_data)
            .await
            .map_err(|e| {
                AppError::Internal(format!("Failed to store data file in DVC cache: {}", e))
            })?;

        // 2. Generate DVC metadata
        let dvc_content = generate_dvc_file(&file_name, &file_hash)
            .map_err(|e| AppError::Internal(format!("Failed to generate DVC metadata: {}", e)))?;
        let dvc_filename = format!("{}.dvc", file_name);

        // 3. Store DVC metadata file
        let dvc_path = state
            .storage_service
            .store_dvc_metadata(&dvc_filename, &dvc_content)
            .await
            .map_err(|e| AppError::Internal(format!("Failed to store DVC metadata: {}", e)))?;

        // 4. Create unified data items using transaction
        let (data_item, dvc_item) = state
            .data_service
            .create_unified_data_item(
                user.id, // This is correct since upload_data gets real user from auth_service
                &file_name,
                &dvc_filename,
                description.clone(),
                project.clone(),
                source.clone().unwrap_or_else(|| "web-upload".to_string()),
                Some(file_hash.clone()),
                Some(file_data.len() as i64),
                None,  // file_type
                false, // is_folder
                None,  // file_count
                None,  // parent_id
                is_public,
            )
            .await
            .map_err(|e| {
                AppError::Internal(format!("Failed to create unified data items: {}", e))
            })?;

        // 5. Update file paths in database
        if let Some(ref dvc_ref) = dvc_item {
            // Calculate DVC file hash for metadata
            let dvc_file_hash = calculate_file_hash(dvc_content.as_bytes()).map_err(|e| {
                AppError::Internal(format!("Failed to calculate DVC file hash: {}", e))
            })?;

            let _dvc_updated = state
                .dvc_metadata_service
                .update_dvc_file_path(dvc_ref.id, &dvc_path, Some(dvc_file_hash))
                .await?;
        }

        // For data item, we use the hash-based cache path
        let cache_path = format!("cache/{}/{}", &file_hash[0..2], &file_hash[2..]);
        let _data_updated = state
            .data_service
            .update_file_path(data_item.id, &cache_path)
            .await?;

        uploaded_items.push((data_item.clone(), dvc_item.clone()));

        println!(
            "DEBUG: Successfully uploaded unified items: data={}, dvc={:?}",
            data_item.id,
            dvc_item.as_ref().map(|d| d.id)
        );
    }

    Ok((
        StatusCode::OK,
        Json(serde_json::json!({
            "message": "Files uploaded with DVC metadata",
            "uploaded_items": uploaded_items.len(),
            "items": uploaded_items
        })),
    ))
}

pub async fn delete_data_item(
    Path(id): Path<i64>,
    AuthenticatedUser(user): AuthenticatedUser,
    State(state): State<crate::AppState>,
) -> Result<StatusCode, AppError> {
    // Get real user_id from database using email
    let user_id = state.data_service.get_user_id_by_email(&user.email).await?;

    // Get item info before deletion
    let item = state
        .data_service
        .get_data_item(id, user_id)
        .await?
        .ok_or(AppError::NotFound("Data item not found".to_string()))?;

    // Policy check: Public data cannot be deleted by users
    if item.is_public {
        return Err(AppError::BadRequest(
            "Public data cannot be deleted. Contact an administrator for removal.".to_string(),
        ));
    }

    let deleted = state.data_service.delete_data_item(id, user_id).await?;

    if deleted {
        // Delete file from storage
        let _ = state.storage_service.delete_file(&item.file_path).await;
        Ok(StatusCode::NO_CONTENT)
    } else {
        Err(AppError::NotFound("Data item not found".to_string()))
    }
}

pub async fn download_data_file(
    Path(id): Path<i64>,
    claims: Claims,
    State(state): State<crate::AppState>,
) -> Result<impl IntoResponse, AppError> {
    let user = state.auth_service.get_user_by_email(&claims.sub).await?;
    let user_id = user.id;

    // Get unified data item
    let unified_item = state
        .data_service
        .get_unified_data_item(id, user_id)
        .await?;

    // Get actual data file using unified logic
    let file_data = if let Some(hash) = &unified_item.data_item.file_hash {
        // Use DVC cache for content-addressed storage
        println!(
            "DEBUG: Downloading data file from DVC cache using hash: {}",
            hash
        );
        state
            .storage_service
            .get_data_file_by_hash(hash)
            .await
            .map_err(|e| AppError::Internal(format!("Failed to get data file from cache: {}", e)))?
    } else {
        // Fallback to direct file path (for items without hash)
        println!(
            "DEBUG: Downloading data file from direct path: {}",
            unified_item.actual_file_path
        );
        state
            .storage_service
            .get_file(&unified_item.actual_file_path)
            .await
            .map_err(|e| AppError::Internal(format!("Failed to get data file: {}", e)))?
    };

    let filename = &unified_item.data_item.name;

    Ok((
        StatusCode::OK,
        [(header::CONTENT_TYPE, "application/octet-stream")],
        [(
            header::CONTENT_DISPOSITION,
            format!("attachment; filename=\"{}\"", filename),
        )],
        file_data,
    ))
}

pub async fn download_dvc_file(
    Path(id): Path<i64>,
    AuthenticatedUser(user): AuthenticatedUser,
    State(state): State<crate::AppState>,
) -> Result<impl IntoResponse, AppError> {
    println!(
        "DEBUG: download_dvc_file called for item ID: {}, user: {}",
        id, user.email
    );

    // Get real user_id from database using email
    let user_id = state.data_service.get_user_id_by_email(&user.email).await?;

    // Get unified data item
    let unified_item = state
        .data_service
        .get_unified_data_item(id, user_id)
        .await?;

    println!(
        "DEBUG: Got unified item: {}, has_dvc_metadata: {}",
        unified_item.data_item.name, unified_item.has_dvc_metadata
    );

    // Get DVC metadata using unified logic
    let dvc_content = if let Some(dvc_metadata) = &unified_item.dvc_metadata {
        // Use existing DVC metadata file if path is not empty
        if !dvc_metadata.file_path.is_empty() {
            println!(
                "DEBUG: Downloading existing DVC metadata: {}",
                dvc_metadata.file_path
            );
            let content = state
                .storage_service
                .get_file(&dvc_metadata.file_path)
                .await
                .map_err(|e| {
                    AppError::Internal(format!("Failed to get DVC metadata file: {}", e))
                })?;
            String::from_utf8(content)
                .map_err(|e| AppError::Internal(format!("DVC metadata not valid UTF-8: {}", e)))?
        } else {
            // DVC metadata exists but file path is empty, generate content
            println!(
                "DEBUG: DVC metadata exists but file path is empty, generating content for: {}",
                unified_item.data_item.name
            );
            if let Some(hash) = &unified_item.data_item.file_hash {
                crate::utils::dvc::generate_dvc_file(&unified_item.data_item.name, hash).map_err(
                    |e| AppError::Internal(format!("Failed to generate DVC content: {}", e)),
                )?
            } else {
                return Err(AppError::NotFound(
                    "No file hash available for DVC generation".to_string(),
                ));
            }
        }
    } else {
        // Generate DVC content for data item
        if let Some(hash) = &unified_item.data_item.file_hash {
            println!(
                "DEBUG: Generating DVC content for data item: {}",
                unified_item.data_item.name
            );
            crate::utils::dvc::generate_dvc_file(&unified_item.data_item.name, hash)
                .map_err(|e| AppError::Internal(format!("Failed to generate DVC content: {}", e)))?
        } else {
            return Err(AppError::NotFound(
                "No file hash available for DVC generation".to_string(),
            ));
        }
    };

    let dvc_filename = format!("{}.dvc", unified_item.data_item.name);
    println!("DEBUG: Returning DVC file: {}", dvc_filename);

    Ok((
        StatusCode::OK,
        [(header::CONTENT_TYPE, "text/yaml")],
        [(
            header::CONTENT_DISPOSITION,
            format!("attachment; filename=\"{}\"", dvc_filename),
        )],
        dvc_content,
    ))
}

pub async fn get_dvc_content(
    Path(id): Path<i64>,
    AuthenticatedUser(user): AuthenticatedUser,
    State(state): State<crate::AppState>,
) -> Result<impl IntoResponse, AppError> {
    // Get real user_id from database using email
    let user_id = state.data_service.get_user_id_by_email(&user.email).await?;

    // Get unified data item
    let unified_item = state
        .data_service
        .get_unified_data_item(id, user_id)
        .await?;

    // Get DVC content using unified logic
    let dvc_content = if let Some(dvc_metadata) = &unified_item.dvc_metadata {
        // Use existing DVC metadata file
        let content = state
            .storage_service
            .get_file(&dvc_metadata.file_path)
            .await
            .map_err(|e| AppError::Internal(format!("Failed to get DVC metadata file: {}", e)))?;
        String::from_utf8(content)
            .map_err(|e| AppError::Internal(format!("DVC metadata not valid UTF-8: {}", e)))?
    } else {
        // Generate DVC content for data item
        if let Some(hash) = &unified_item.data_item.file_hash {
            crate::utils::dvc::generate_dvc_file(&unified_item.data_item.name, hash)
                .map_err(|e| AppError::Internal(format!("Failed to generate DVC content: {}", e)))?
        } else {
            return Err(AppError::NotFound(
                "No file hash available for DVC generation".to_string(),
            ));
        }
    };
    // Return raw YAML content as plain text
    Ok((
        StatusCode::OK,
        [(header::CONTENT_TYPE, "text/yaml")],
        dvc_content,
    ))
}

pub async fn upload_metadata(
    Path(id): Path<i64>,
    State(state): State<crate::AppState>,
    AuthenticatedUser(user): AuthenticatedUser,
    Json(metadata): Json<MetadataUpload>,
) -> Result<Json<crate::models::DataItem>, AppError> {
    // Get real user_id from database using email
    let user_id = state.data_service.get_user_id_by_email(&user.email).await?;

    let updated_item = state
        .data_service
        .update_data_item(
            id,
            user_id,
            Some(metadata.name),
            metadata.description,
            None, // project
            Some(metadata.is_public.unwrap_or(false)),
        )
        .await?;

    Ok(Json(updated_item))
}

pub async fn make_data_public(
    Path(id): Path<i64>,
    AuthenticatedUser(user): AuthenticatedUser,
    State(state): State<crate::AppState>,
) -> Result<StatusCode, AppError> {
    // Get real user_id from database using email
    let user_id = state.data_service.get_user_id_by_email(&user.email).await?;

    // Update public status to true
    state
        .data_service
        .update_public_status(id, user_id, true)
        .await?;

    Ok(StatusCode::OK)
}

pub async fn upload_dvc_metadata(
    State(state): State<crate::AppState>,
    mut multipart: Multipart,
) -> Result<impl IntoResponse, AppError> {
    let mut dvc_file_content = None;
    let mut username = None;
    let mut _project_name = None;

    while let Some(field) = multipart
        .next_field()
        .await
        .map_err(|e| AppError::Internal(format!("Failed to read multipart field: {}", e)))?
    {
        let name = field.name().unwrap_or("").to_string();

        match name.as_str() {
            "dvc_file" => {
                let file_name = field.file_name().unwrap_or("unknown.dvc").to_string();
                let file_data = field.bytes().await.map_err(|e| {
                    AppError::Internal(format!("Failed to read DVC file data: {}", e))
                })?;

                println!(
                    "DEBUG: Received DVC file: {} ({} bytes)",
                    file_name,
                    file_data.len()
                );
                dvc_file_content = Some((file_name, file_data));
            }
            "username" => {
                username =
                    Some(field.text().await.map_err(|e| {
                        AppError::Internal(format!("Failed to read username: {}", e))
                    })?);
            }
            "project_name" => {
                _project_name = Some(field.text().await.map_err(|e| {
                    AppError::Internal(format!("Failed to read project name: {}", e))
                })?);
            }
            _ => {
                println!("DEBUG: Ignoring unknown field: {}", name);
            }
        }
    }

    if dvc_file_content.is_none() {
        return Err(AppError::BadRequest("No DVC file provided".to_string()));
    }

    let (file_name, file_data) = dvc_file_content.unwrap();

    // Parse username to get user ID, or create user if doesn't exist
    let user_id = if let Some(username_str) = username {
        // Extract email from username format (user@domain.com)
        if let Some(email) = username_str.split('@').next() {
            format!("{}@hillstonenet.com", email)
        } else {
            username_str
        }
    } else {
        return Err(AppError::BadRequest("Username required".to_string()));
    };

    // Try to get existing user, or create a new one
    let user = match state.auth_service.get_user_by_email(&user_id).await {
        Ok(user) => user,
        Err(_) => {
            // Create a new user automatically for git hook operations
            let user_create = crate::models::UserCreate {
                email: user_id.clone(),
                password: "hillstone".to_string(), // TODO: unnecessary to create new user?
            };
            state.auth_service.create_user(&user_create, false).await?
        }
    };

    // Parse DVC content to get actual file info
    let dvc_content = String::from_utf8(file_data.to_vec())
        .map_err(|e| AppError::Internal(format!("Failed to parse DVC content as UTF-8: {}", e)))?;

    let metadata = crate::utils::dvc::parse_dvc_file(&dvc_content)
        .map_err(|e| AppError::Internal(format!("Failed to parse DVC metadata: {}", e)))?;

    if let Some(output) = metadata.outs.first() {
        let actual_filename = &output.path;
        let expected_hash = &output.md5;

        println!(
            "DEBUG: DVC metadata references file: {} with hash: {}",
            actual_filename, expected_hash
        );

        // For git hook uploads, we assume the data file is already in DVC cache
        // We'll create the unified structure but won't transfer the actual data

        // 1. Store DVC metadata file
        let _dvc_path = state
            .storage_service
            .store_dvc_metadata(&file_name, &dvc_content)
            .await
            .map_err(|e| AppError::Internal(format!("Failed to store DVC metadata: {}", e)))?;

        // 2. Check for existing DVC metadata to prevent duplicates
        if let Some(existing_dvc) = state
            .dvc_metadata_service
            .get_dvc_metadata_by_filename_and_user(&file_name, user.id)
            .await
            .map_err(|e| {
                AppError::Internal(format!("Failed to check for existing DVC metadata: {}", e))
            })?
        {
            println!("DEBUG: Git hook found existing DVC metadata {} with filename {}, skipping creation", 
                     existing_dvc.id, file_name);
            return Ok((
                StatusCode::OK,
                Json(serde_json::json!({
                    "message": "DVC metadata already exists",
                    "dvc_file": file_name,
                    "data_file": actual_filename,
                    "existing_dvc_item_id": existing_dvc.id,
                    "user": user_id
                })),
            ));
        }

        // 3. Create DVC metadata record in new table (ONLY - no data_items creation)
        let _dvc_hash = calculate_file_hash(dvc_content.as_bytes())?;
        let dvc_metadata = state
            .dvc_metadata_service
            .create_dvc_metadata_with_content(user.id, &file_name, Some(&dvc_content))
            .await
            .map_err(|e| AppError::Internal(format!("Failed to create DVC metadata: {}", e)))?;

        println!(
            "DEBUG: Successfully created DVC metadata record from git hook: dvc={}",
            dvc_metadata.id
        );

        return Ok((
            StatusCode::OK,
            Json(serde_json::json!({
                "message": "DVC metadata uploaded successfully",
                "dvc_file": file_name,
                "data_file": actual_filename,
                "dvc_item_id": dvc_metadata.id,
                "user": user_id
            })),
        ));
    }

    Err(AppError::BadRequest(
        "Invalid DVC file format - no outputs found".to_string(),
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_upload_data_success() {
        // Test successful file upload
        let test_file_content = b"Hello, World!";
        let file_hash = calculate_file_hash(test_file_content).unwrap();

        // Test file hash calculation
        assert!(!file_hash.is_empty());
        assert_eq!(file_hash.len(), 32); // MD5 hex string length
    }

    #[tokio::test]
    async fn test_upload_data_empty_files() {
        // Test that empty file upload returns appropriate error
        // This would test the validation logic in upload_data
        let empty_files: Vec<(String, Vec<u8>)> = Vec::new();
        assert!(empty_files.is_empty());
    }

    #[tokio::test]
    async fn test_upload_data_large_file() {
        // Test handling of large files
        let large_content = vec![0u8; 10 * 1024 * 1024]; // 10MB
        let file_hash = calculate_file_hash(&large_content).unwrap();

        assert!(!file_hash.is_empty());
        assert_eq!(file_hash.len(), 64);
    }

    #[tokio::test]
    async fn test_metadata_upload_validation() {
        // Test metadata validation
        let valid_metadata = MetadataUpload {
            name: "test_file.txt".to_string(),
            description: Some("Test description".to_string()),
            is_public: Some(true),
        };

        assert!(!valid_metadata.name.is_empty());
        assert!(valid_metadata.is_public.unwrap());
    }

    #[tokio::test]
    async fn test_list_query_parsing() {
        // Test query parameter parsing
        let query = ListQuery {
            page: Some(2),
            page_size: Some(50),
            user_only: Some(true),
        };

        assert_eq!(query.page.unwrap(), 2);
        assert_eq!(query.page_size.unwrap(), 50);
        assert!(query.user_only.unwrap());
    }

    #[tokio::test]
    async fn test_file_hash_consistency() {
        // Test that file hash calculation is consistent
        let content = b"test content for hashing";
        let hash1 = calculate_file_hash(content).unwrap();
        let hash2 = calculate_file_hash(content).unwrap();

        assert_eq!(hash1, hash2);
    }

    #[tokio::test]
    async fn test_file_hash_uniqueness() {
        // Test that different content produces different hashes
        let content1 = b"content one";
        let content2 = b"content two";
        let hash1 = calculate_file_hash(content1).unwrap();
        let hash2 = calculate_file_hash(content2).unwrap();

        assert_ne!(hash1, hash2);
    }
}
